/**
 * @file app\utils\storage.c
 *
 * Copyright (C) 2023
 *
 * storage.c is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author HinsShum hinsshum@qq.com
 *
 * @encoding utf-8
 */

/*---------- includes ----------*/
#include "storage.h"
#include "device.h"
#include "flash_map.h"
#include "misc.h"
#include "options.h"
#include "protocol_mobus.h"
#include "resources.h"
#include "soft_timer.h"
#include "tasks.h"
#include <string.h>

/*---------- macro ----------*/
#define TAG "Storage"
#define CONFIG_STORAGE_FIRST (0xAA)
#define CONFIG_CLEAR_UPDATA_3S 3
#define CONFIG_EE_WRITE_MAX_TIMES 1000
#define CONFIG_EE_WRITE_TIMES_ADD CONFIG_AT24CXX_END - 8
struct storage {
    uint8_t factory_first_id;
    uint16_t write_nums;
    struct storage_mobus_device mobus_device;
    struct storage_led_struct led_struct;
    struct storage_lianpai_struct lianpai_struct;
    struct storage_ambient_struct ambient_struct;
    struct storage_motor_struct motor_struct;
    struct storage_pir_struct pir_struct;
    struct storage_key_struct key_struct;
    struct storage_relay_struct relay_struct;
    struct storage_mobus_groups_info mobus_groups;
};

/*---------- variable ----------*/
static void* at24cxx;
unsigned char clear_updata = 0;
temp_variable no_storage;
/* storage cache
 */
static struct storage storage_temp;
static struct storage storage_cache;
static volatile uint32_t addition = CONFIG_AT24CXX_BASE;
/*---------- function ----------*/
static uint32_t protect_ee_task(void) {
    volatile unsigned char temp = 0;
    if (storage_cache.write_nums != 0) {
        temp = storage_cache.write_nums % CONFIG_EE_WRITE_MAX_TIMES;
        addition = CONFIG_AT24CXX_BASE +
            (storage_cache.write_nums / 5) * sizeof(struct storage);
        if (addition > (CONFIG_AT24CXX_END - (4 * sizeof(struct storage)))) {
            addition = CONFIG_AT24CXX_BASE;
            storage_cache.write_nums = 0;
        }
    }
    else
        addition = CONFIG_AT24CXX_BASE;
    return addition;
}

static void _set_default() {
  /* set first write flag */
    storage_cache.factory_first_id = CONFIG_STORAGE_FIRST;
    /* set mobus device */
    storage_cache.mobus_device.factory_reset_flag = 0;
    memset(&storage_cache.mobus_device.sn, 0,
        sizeof(storage_cache.mobus_device.sn));
    storage_cache.mobus_device.device.native_address = 0xFF;
    storage_cache.mobus_device.device.device_type = MOBUS_DEVICE_TYPE_LSKED;
    storage_cache.mobus_device.device.channel_nums = CONFIG_STORAGE_MOBUS_CHANNEL_NUMS;
    for (uint8_t i = 1; i < CONFIG_STORAGE_MOBUS_CHANNEL_NUMS; i++)
        storage_cache.mobus_device.device.channel_type[i] = MOBUS_DEVICE_TYPE_UNKNOWN;
      /* set led mode */
    storage_cache.led_struct.led_mode = full_bright;
    storage_cache.led_struct.assign_bright = 2.5 * _led_value_max;
    storage_cache.led_struct.led_level = _led_level_10;
    storage_cache.led_struct.LisicarD_struct.background = 0;
    storage_cache.led_struct.LisicarD_struct.LisicarD_sys = 0x3D;
    /* set board mode */

    /* set lianpai mode */
    storage_cache.lianpai_struct.lianpai_mode_max = 1;
    storage_cache.lianpai_struct.lianpai_mode_num = 1;
    /* set mobus groups */
    for (uint8_t ii = 0; ii < ARRAY_SIZE(storage_cache.mobus_groups.groups);
        ++ii) {
        storage_cache.mobus_groups.groups[ii].uid = 0xFFFF;
        storage_cache.mobus_groups.groups[ii].bits = 0x00;
    }
    /* set ambient struct */
    storage_cache.ambient_struct.brightness = 100;
    storage_cache.ambient_struct.cw_temperature = 4600;
    storage_cache.ambient_struct.hsi = 100;
    storage_cache.ambient_struct.beiguang_meeting_ambient_percentage = 40;
    /* set motor struct */
    storage_cache.motor_struct.left_motor_time = 16;
    storage_cache.motor_struct.left_shock_proofness = 16;
    storage_cache.motor_struct.left_motor_lever = _motor_level_9;
    storage_cache.motor_struct.right_motor_lever = _motor_level_9;
    storage_cache.motor_struct.right_motor_time = 16;
    storage_cache.motor_struct.right_shock_proofness = 16;
    /* set pir struct */
    storage_cache.pir_struct.pir_enanle_flag = 1;
    storage_cache.pir_struct.pir_keylight_born_time = 0;
    storage_cache.pir_struct.pir_keylight_keep_time = 15;
    storage_cache.pir_struct.pir_keylight_die_time = 5;
    storage_cache.pir_struct.pir_distance = 0;
    /* set key struct */
    storage_cache.key_struct.dis_en_flag = 0xffff;
    for (uint8_t iii = 0; iii < 16; iii++) {
        storage_cache.key_struct.item[iii].key_onoff = 1;
        storage_cache.key_struct.item[iii].buf[0] = 0;
        storage_cache.key_struct.item[iii].datalen = 0;
        storage_cache.key_struct.item[iii].flag = 0;
        storage_cache.key_struct.item[iii].tg_id = 0;
        storage_cache.key_struct.item[iii].type = 0;
    }
    /* set relay struct */
    storage_cache.relay_struct.relay_sta = 0;
    clear_updata = CONFIG_CLEAR_UPDATA_3S;
}
char compareStructs(const struct storage* a, const struct storage* b) {
    char tt = 0;
    tt = memcmp(a, b, sizeof(struct storage));
    return tt;
}
static void _timer_expired(timer_handle_t timer) {
    uint8_t tempbuff[2] = { 0 };
    if (no_storage.allocate_adr_dly > 0) {
        no_storage.allocate_adr_dly--;
    }
    if (clear_updata > 0) {
        clear_updata--;
        if (clear_updata == 0) {
            storage_cache.write_nums++;
            if (storage_cache.write_nums > 10000)
                storage_cache.write_nums = 0;
            device_write(at24cxx, &storage_cache.write_nums,
                CONFIG_EE_WRITE_TIMES_ADD, sizeof(storage_cache.write_nums));
            addition = protect_ee_task();
            device_write(at24cxx, &storage_cache, addition, sizeof(struct storage));
            device_read(at24cxx, &storage_temp, addition, sizeof(struct storage));
            if (compareStructs(&storage_cache, &storage_temp) != 0) // TODO: check write date is err then report it
            {
                tempbuff[0] = 0x01;
                tempbuff[1] = 0x01;
                protocol_mobus_dev_report(MOBUS_FRAME_TYPE_REPORT_ERROR, 1, tempbuff, 2);
            }
        }
    }
}
void no_storages_init(struct storage x) {
    no_storage.allocate_adr_dly = 30;
    no_storage.led_temp_variable.dev_work_mode = LED_MODE_START;
    no_storage.LisicarD_Temp.s1_led_sat = storage_cache.led_struct.LisicarD_struct.background & 0x01;
    no_storage.LisicarD_Temp.s2_led_sat = (storage_cache.led_struct.LisicarD_struct.background >> 1) & 0x01;
    no_storage.LisicarD_Temp.s3_led_sat = (storage_cache.led_struct.LisicarD_struct.background >> 2) & 0x01;
    no_storage.LisicarD_Temp.s4_led_sat = (storage_cache.led_struct.LisicarD_struct.background >> 3) & 0x01;
    no_storage.LisicarD_Temp.s5_led_sat = (storage_cache.led_struct.LisicarD_struct.background >> 4) & 0x01;
    no_storage.LisicarD_Temp.s6_led_sat = (storage_cache.led_struct.LisicarD_struct.background >> 5) & 0x01;
    no_storage.LisicarD_Temp.s7_led_sat = (storage_cache.led_struct.LisicarD_struct.background >> 6) & 0x01;
    no_storage.LisicarD_Temp.s8_led_sat = (storage_cache.led_struct.LisicarD_struct.background >> 7) & 0x01;

    no_storage.LisicarD_Temp.backgroud_led = (storage_cache.led_struct.LisicarD_struct.background >> 8) & 0x01;
    no_storage.LisicarD_Temp.null_1 = (storage_cache.led_struct.LisicarD_struct.background >> 9) & 0x01;
    no_storage.LisicarD_Temp.null_2 = (storage_cache.led_struct.LisicarD_struct.background >> 10) & 0x01;
    no_storage.LisicarD_Temp.null_3 = (storage_cache.led_struct.LisicarD_struct.background >> 11) & 0x01;
    no_storage.LisicarD_Temp.s9_led_sat = (storage_cache.led_struct.LisicarD_struct.background >> 12) & 0x01;
    no_storage.LisicarD_Temp.s10_led_sat = (storage_cache.led_struct.LisicarD_struct.background >> 13) & 0x01;
    no_storage.LisicarD_Temp.s11_led_sat = (storage_cache.led_struct.LisicarD_struct.background >> 14) & 0x01;
    no_storage.LisicarD_Temp.s12_led_sat = (storage_cache.led_struct.LisicarD_struct.background >> 15) & 0x01;

    no_storage.LisicarD_Temp.autoplay_mode = storage_cache.led_struct.LisicarD_struct.LisicarD_sys & 0x01;
    no_storage.LisicarD_Temp.net_mode = (storage_cache.led_struct.LisicarD_struct.LisicarD_sys >> 1) & 0x01;
    no_storage.LisicarD_Temp.key_voluntary_mode = (storage_cache.led_struct.LisicarD_struct.LisicarD_sys >> 2) & 0x01;
    no_storage.LisicarD_Temp.pir_enable_flag = (storage_cache.led_struct.LisicarD_struct.LisicarD_sys >> 3) & 0x01;
    no_storage.LisicarD_Temp.ledx_dlydie_flag = (storage_cache.led_struct.LisicarD_struct.LisicarD_sys >> 4) & 0x01;
    no_storage.LisicarD_Temp.press_up_send_enable = (storage_cache.led_struct.LisicarD_struct.LisicarD_sys >> 5) & 0x01;
}
bool storage_init(void) {
    bool err = false;
    volatile unsigned int block_size = 0, i = 0;
    static void* proc_timer;
    at24cxx = resources_get("dev_at24cxx");
    assert(at24cxx);
    device_read(at24cxx, &storage_cache.write_nums, CONFIG_EE_WRITE_TIMES_ADD,
        sizeof(storage_cache.write_nums));
    device_read(at24cxx, &storage_cache, protect_ee_task(),
        sizeof(struct storage));
    device_read(at24cxx, &storage_cache.write_nums, CONFIG_EE_WRITE_TIMES_ADD,
        sizeof(storage_cache.write_nums));
    do {
        if (storage_cache.factory_first_id != CONFIG_STORAGE_FIRST)
            _set_default();
        /* init temp no_storage struct */
        no_storages_init(storage_cache);
        /* create a timer for proc polling */
        proc_timer = soft_timer_create("Storage_ProcTimer", SFTIM_MODE_REPEAT,
            __ms2ticks(1000), NULL, _timer_expired);
        assert(proc_timer);
        soft_timer_start(proc_timer);
        err = true;
        /* say hi */
        xlog_tag_message(TAG, "Initialize successfully\n");
    } while (0);

    return err;
}
void* storage_get(storage_area_t area) {
    bool err = false;
    void* valuePointer = NULL;
    assert(area < STORAGE_AREA_RELAY);
    if (area == STORAGE_AREA_LIGHT_MODE) {
        valuePointer = &storage_cache.led_struct;
    }
    else if (area == STORAGE_AREA_LIANPAN) {
        valuePointer = &storage_cache.lianpai_struct;
    }
    else if (area == STORAGE_AREA_MOBUS_GROUPS) {
        valuePointer = &storage_cache.mobus_groups;
    }
    else if (area == STORAGE_AREA_AMBIENT) {
        valuePointer = &storage_cache.ambient_struct;
    }
    else if (area == STORAGE_AREA_DEVICE) {
        valuePointer = &storage_cache.mobus_device;
    }
    else if (area == STORAGE_AREA_MOTOR) {
        valuePointer = &storage_cache.motor_struct;
    }
    else if (area == STORAGE_AREA_PIR) {
        valuePointer = &storage_cache.pir_struct;
    }
    else if (area == STORAGE_AREA_KEY) {
        valuePointer = &storage_cache.key_struct;
    }
    else if (area == STORAGE_AREA_RELAY) {
        valuePointer = &storage_cache.relay_struct;
    }

    return valuePointer;
}

bool storage_set(storage_area_t area, void* value, uint32_t size) {
    bool err = false;
    assert(area < STORAGE_AREA_RELAY);
    assert(size);
    clear_updata = CONFIG_CLEAR_UPDATA_3S;
    if (area == STORAGE_AREA_LIGHT_MODE) {
        struct storage_led_struct* p = (struct storage_led_struct*)value;
        memcpy(&storage_cache.led_struct, p, sizeof(struct storage_led_struct));
    }
    else if (area == STORAGE_AREA_LIANPAN) {
        struct storage_lianpai_struct* p = (struct storage_lianpai_struct*)value;
        memcpy(&storage_cache.led_struct, p, sizeof(struct storage_lianpai_struct));
    }
    else if (area == STORAGE_AREA_MOBUS_GROUPS) {
        struct storage_mobus_groups_info* p =
            (struct storage_mobus_groups_info*)value;
        memcpy(&storage_cache.mobus_groups, p, sizeof(storage_cache.mobus_groups));
    }
    else if (area == STORAGE_AREA_AMBIENT) {
        struct storage_ambient_struct* p = (struct storage_ambient_struct*)value;
        memcpy(&storage_cache.ambient_struct, p,
            sizeof(storage_cache.ambient_struct));
    }
    else if (area == STORAGE_AREA_DEVICE) {
        struct storage_mobus_device* p = (struct storage_mobus_device*)value;
        memcpy(&storage_cache.mobus_device, p, sizeof(storage_cache.mobus_device));
    }
    else if (area == STORAGE_AREA_MOTOR) {
        struct storage_motor_struct* p = (struct storage_motor_struct*)value;
        memcpy(&storage_cache.motor_struct, p, sizeof(storage_cache.motor_struct));
    }
    else if (area == STORAGE_AREA_PIR) {
        struct storage_pir_struct* p = (struct storage_pir_struct*)value;
        memcpy(&storage_cache.pir_struct, p, sizeof(storage_cache.pir_struct));
    }
    else if (area == STORAGE_AREA_KEY) {
        struct storage_key_struct* p = (struct storage_key_struct*)value;
        memcpy(&storage_cache.key_struct, p, sizeof(storage_cache.key_struct));
    }
    else if (area == STORAGE_AREA_RELAY) {
        struct storage_relay_struct* p = (struct storage_relay_struct*)value;
        memcpy(&storage_cache.relay_struct, p, sizeof(storage_cache.relay_struct));
    }
    return err;
}

bool storage_restore(storage_area_t area) {
    bool err = false;
    clear_updata = CONFIG_CLEAR_UPDATA_3S;
    if (area == STORAGE_AREA_LIGHT_MODE) {
        storage_cache.led_struct.led_mode = full_bright;
        storage_cache.led_struct.assign_bright = 2.5 * _led_value_max;
        storage_cache.led_struct.led_level = _led_level_10;
    }
    else if (area == STORAGE_AREA_LIANPAN) {
        storage_cache.lianpai_struct.lianpai_mode_max = 1;
        storage_cache.lianpai_struct.lianpai_mode_num = 1;
    }
    else if (area == STORAGE_AREA_MOBUS_GROUPS) {
        for (uint8_t ii = 0; ii < ARRAY_SIZE(storage_cache.mobus_groups.groups);
            ++ii) {
            storage_cache.mobus_groups.groups[ii].uid = 0xFFFF;
            storage_cache.mobus_groups.groups[ii].bits = 0x00;
        }
    }
    else if (area == STORAGE_AREA_AMBIENT) {
        storage_cache.ambient_struct.brightness = 100;
        storage_cache.ambient_struct.cw_temperature = 4600;
        storage_cache.ambient_struct.hsi = 100;
        storage_cache.ambient_struct.beiguang_meeting_ambient_percentage = 40;
    }
    else if (area == STORAGE_AREA_DEVICE) {
        storage_cache.mobus_device.factory_reset_flag = 0;
        memset(&storage_cache.mobus_device.sn, 0,
            sizeof(storage_cache.mobus_device.sn));
        storage_cache.mobus_device.device.native_address = 0xFF;
        storage_cache.mobus_device.device.device_type = MOBUS_DEVICE_TYPE_LSKED;
        storage_cache.mobus_device.device.channel_nums =
            CONFIG_STORAGE_MOBUS_CHANNEL_NUMS;
        for (uint8_t i = 1; i < CONFIG_STORAGE_MOBUS_CHANNEL_NUMS; i++)
            storage_cache.mobus_device.device.channel_type[i] =
            MOBUS_DEVICE_TYPE_UNKNOWN;
    }
    else if (area == STORAGE_AREA_MOTOR) {
        storage_cache.motor_struct.left_motor_time = 16;
        storage_cache.motor_struct.left_shock_proofness = 16;
        storage_cache.motor_struct.left_motor_lever = _motor_level_9;
        storage_cache.motor_struct.right_motor_lever = _motor_level_9;
        storage_cache.motor_struct.right_motor_time = 16;
        storage_cache.motor_struct.right_shock_proofness = 16;
    }
    else if (area == STORAGE_AREA_PIR) {
        storage_cache.pir_struct.pir_enanle_flag = 1;
        storage_cache.pir_struct.pir_keylight_born_time = 0;
        storage_cache.pir_struct.pir_keylight_keep_time = 60;
        storage_cache.pir_struct.pir_keylight_die_time = 5;
        storage_cache.pir_struct.pir_distance = 0;
    }
    else if (area == STORAGE_AREA_KEY) {
        storage_cache.key_struct.dis_en_flag = 0xffff;
        for (uint8_t iii = 0; iii < 16; iii++) {
            storage_cache.key_struct.item[iii].key_onoff = 1;
            storage_cache.key_struct.item[iii].buf[0] = 0;
            storage_cache.key_struct.item[iii].datalen = 0;
            storage_cache.key_struct.item[iii].flag = 0;
            storage_cache.key_struct.item[iii].tg_id = 0;
            storage_cache.key_struct.item[iii].type = 0;
        }
    }
    else if (area == STORAGE_AREA_RELAY) {
        storage_cache.relay_struct.relay_sta = 0;
    }

    return err;
}
